using System;
using SharpMud.Net.Telnet.IAC;

namespace SharpMud.Net.Telnet.Session.DataProcessors.IAC
{
	public class OutputLayer : System.IO.IDataProcessor
	{	
		private System.Collections.IQueue<byte>	_ParsedDataQueue;
		private System.Collections.IQueue<byte>	_UnparsedDataQueue;
		private IACTelnetLayer								_HostLayer;
		private bool										_Enabled;

		#region Constructors
		private OutputLayer()
		{
		}

		internal OutputLayer(IACTelnetLayer hostLayer)
		{
			_HostLayer = hostLayer;
			//TODO: Resolve IAC issues causing Telnet glitches
			_Enabled = false;
		}
		#endregion

		#region Public Properties
		public IACTelnetLayer HostLayer
		{
			get
			{
				return _HostLayer;
			}
		}
		public Net.Telnet.Session.TelnetSessionBase HostSession
		{
			get
			{
				return HostLayer.HostSession;
			}
		}
		public bool Enabled
		{
		get
		{
			return _Enabled;
		}
		set{
			_Enabled = value;
		}
		}
		#endregion

		#region IDataProcessor Members

		public System.Collections.IQueue<byte> OutputQueue
		{
			get
			{
				return this._ParsedDataQueue;
			}
		}

		public System.Collections.IQueue<byte> InputQueue
		{
			get
			{
				return this._UnparsedDataQueue;
			}
		}

		public void Initialize(System.Collections.IQueue<byte> inputQueue, System.Collections.IQueue<byte> outputQueue)
		{
			this._UnparsedDataQueue = inputQueue;
			this._ParsedDataQueue = outputQueue;
		}

		public void Pulse()
		{
			byte b;
			while(this._UnparsedDataQueue.Count>0)
			{
				b = this._UnparsedDataQueue.Dequeue();
				if(b==(byte)ControlCodes.IAC)
				{
					this._ParsedDataQueue.Enqueue((byte)ControlCodes.IAC);
					this._ParsedDataQueue.Enqueue((byte)ControlCodes.IAC);
				}
				else
				{
					this._ParsedDataQueue.Enqueue(b);
				}
			}
		}

		#endregion

		public void Send_Other_Packet(ControlCode value)
		{
			if(!_Enabled) return;
			if(IsQuery(value))
			{
				throw new ArgumentOutOfRangeException("value",value.Name,"Parameter 'value' cannot be a query!");
			}
			if((byte)value==(byte)ControlCodes.SB)
			{
				throw new ArgumentOutOfRangeException("value",value.Name,"Parameter 'value' cannot be a subrequest-begin designation!");
			}
			if((byte)value==(byte)ControlCodes.SE)
			{
				throw new ArgumentOutOfRangeException("value",value.Name,"Parameter 'value' cannot be a subrequest-end designation!");
			}
			if((byte)value==(byte)ControlCodes.IAC)
			{
				throw new ArgumentOutOfRangeException("value",value.Name,"Parameter 'value' cannot be IAC!");
			}
			lock(this._ParsedDataQueue)
			{
				this._ParsedDataQueue.Enqueue((byte)ControlCodes.IAC);
				this._ParsedDataQueue.Enqueue((byte)value);
                this._HostLayer.HostSession.Flush();
				if(this.IAC_Other!=null)
					this.IAC_Other(this,new IACOutputEventArgs(value.Value));
			}
		}

		public void Send_FeatureNegotiation_Packet(ControlCode query, ControlCode feature)
		{
			if(!_Enabled) return;
			if(!IsQuery(query))
			{
				throw new ArgumentOutOfRangeException("query",query.Name,"Parameter 'query' must be a query!");
			}
			lock(this._ParsedDataQueue)
			{
				this._ParsedDataQueue.Enqueue((byte)ControlCodes.IAC);
				this._ParsedDataQueue.Enqueue((byte)query);
				this._ParsedDataQueue.Enqueue((byte)feature);
                this._HostLayer.HostSession.Flush();
				if(this.IAC_Feature_Negotiation!=null)
					this.IAC_Feature_Negotiation(this,new IACOutputEventArgs(query,feature));
			}
		}

		public void Send_Subrequest_Packet(ControlCode featureCode, byte[] rawData)
		{
			if(!_Enabled) return;
			if(IsQuery(featureCode))
			{
				throw new ArgumentOutOfRangeException("featureCode",featureCode.Name,"Parameter 'featureCode' must be a code for a feature, not a query!");
			}
			lock(this._ParsedDataQueue)
			{
				this._ParsedDataQueue.Enqueue((byte)ControlCodes.IAC);
				this._ParsedDataQueue.Enqueue((byte)ControlCodes.SB);
				this._ParsedDataQueue.Enqueue((byte)featureCode);
				this._ParsedDataQueue.Enqueue(IACTelnetLayer.EscapeIAC(rawData));
				this._ParsedDataQueue.Enqueue((byte)ControlCodes.IAC);
				this._ParsedDataQueue.Enqueue((byte)ControlCodes.SE);
                this._HostLayer.HostSession.Flush();
				if(this.IAC_Subrequest!=null)
					this.IAC_Subrequest(this,new IACOutputEventArgs(featureCode,rawData));
			}
		}

		private static bool IsQuery(ControlCode cc)
		{
			if(		((byte)cc==(byte)ControlCodes.DO)	||
					((byte)cc==(byte)ControlCodes.DONT)	||
					((byte)cc==(byte)ControlCodes.WILL)	||
					((byte)cc==(byte)ControlCodes.WONT)	)
				return true;
			else
				return false;
		}
		public event IACOutputEventHandler IAC_Subrequest;
		public event IACOutputEventHandler IAC_Other;
		public event IACOutputEventHandler IAC_Feature_Negotiation;

        #region IDisposable Members

        public void Dispose()
        {
            //Nothing to dispose
        }

        #endregion
    }

	public delegate void IACOutputEventHandler(IAC.OutputLayer sender, IACOutputEventArgs e);

	public class IACOutputEventArgs : System.EventArgs
	{
		private IACSequenceType _Type;

		private byte			_Other;
		private ControlCode		_NegotiationQuery;
		private ControlCode		_FeatureType;
		private byte[]			_SubrequestData;
		
		public IACSequenceType SequenceType
		{
			get
			{
				return this._Type;
			}
		}

		public byte			Other				{get{return _Other;}}
		public ControlCode	NegotiationQuery	{get{return _NegotiationQuery;}}
		public ControlCode	FeatureType			{get{return _FeatureType;}}
		public byte[]		SubrequestData		{get{return _SubrequestData;}}

		private IACOutputEventArgs()
		{
		}
		internal IACOutputEventArgs(byte other)
		{
			this._Type = IACSequenceType.IAC_Other;
			this._Other = other;
		}
		internal IACOutputEventArgs(ControlCode negotiationQuery, ControlCode featureType)
		{
			this._Type = IACSequenceType.IAC_Feature_Negotiation;
			this._NegotiationQuery = negotiationQuery;
			this._FeatureType = featureType;
		}
		internal IACOutputEventArgs(ControlCode featureType,byte[] subrequestData)
		{
			this._Type = IACSequenceType.IAC_Subrequest;
			this._FeatureType = featureType;
			this._SubrequestData = subrequestData;
		}
	}
}
